#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include "Libs/dequeue.h"

#define NUM_CLIENTS 15     //max number of clients we want backlogged on listen
#define MAX_MSG_SIZE 1024  //max size for messages

int pushBottomCommand(char *buffer);
void *serve_client(void *args);
int sendall(int s, char *buf, int *len);
void extractPushStr(char *dqBuf, char *destBuf);
int pushCommand(char *buffer);

void randMutateStr(char *str);

struct thread_args{
	int fd;
	dequeue *dq;
};

int main(int argc, char **argv)
{

	int listener;     // listening socket descriptor
	int newfd;        // newly accept()ed socket descriptor
	char *PORT;			/*DEBUG we might need to check input - this var may be < 0*/
	struct sockaddr_in remoteaddr; // client address
	socklen_t addrlen;

	dequeue *dq;

	fd_set master, readfds;
	int fdmax;

	int yes=1;        // for setsockopt() SO_REUSEADDR, below
	int rv;

	struct addrinfo hints, *ai;

	//threads related vars.
	pthread_t thread;
	int thread_rv;
	struct thread_args eta[1];

	if (argc != 2){
		fprintf(stdout, "ERROR: please enter port number as an argument\n");
		return 0;
	}

	PORT = argv[1];
	
	fprintf(stdout, "SDFGSFDG\n");
	fflush(stdout);
	dq = createDequeue();
	fprintf(stdout, "SDFGSFDG\n");
	fflush(stdout);
	eta[0].dq = dq;

	// get us a socket and bind it

	memset(&hints, 0, sizeof hints);
	hints.ai_family = AF_INET;			//use ipv4
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_PASSIVE;
	if ((rv = getaddrinfo("127.0.0.1", PORT, &hints, &ai)) != 0) {
		fprintf(stdout, "ERROR: %s\n", gai_strerror(rv));   //rv is just for error ref.
		return 0;
	}

	listener = socket(ai->ai_family, ai->ai_socktype, 0);
	if (listener < 0) {
		fprintf(stdout, "ERROR: couldn't get a socket from the OS!!\n");
		freeaddrinfo(ai); // all done with this
		return 0;
	}
	fprintf(stdout,"fd = %d\n", listener);				/*DEBUG*/

	// lose the pesky "address already in use" error message   --- this is another one of beej's functions
	setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));

	if (bind(listener, ai->ai_addr, ai->ai_addrlen) < 0) {
		close(listener);
		freeaddrinfo(ai); // all done with this
		return 0;
	}

	freeaddrinfo(ai); // all done with this

	// listen
	if (listen(listener, 15) == -1) {
		perror("listen");
		close(listener);
		return 0;
	}

	FD_ZERO(&master);
	FD_ZERO(&readfds);

	FD_SET(listener, &master);
	FD_SET(0, &master);
	fdmax = listener;

	printf("DEBUG: started stack-server\npress any key to exit\n");

	// main loop
	while(1) {
		readfds = master; // copy it
		if (select(fdmax+1, &readfds, NULL, NULL, NULL) == -1) {
			perror("select");
			exit(0);
		}

		if (FD_ISSET(listener, &readfds)){
			newfd = accept(listener, (struct sockaddr *)&remoteaddr, &addrlen);
			fprintf(stdout, "new fd is: %d\n", newfd);
			eta[0].fd = newfd;
			thread_rv = pthread_create(&thread, NULL, serve_client, (void *)&eta[0]);
			if (thread_rv){
				fprintf(stderr, "ERROR; return code from pthread_create() is %d\n", thread_rv);
			}
		}

		if (FD_ISSET(0, &readfds)){
			break;
		}
	}

	freeDequeue(dq);
	close(listener);
	return 1;
}



/*beej's function: wrapping a send() function to make sure big buffers
 * are sent properly
 */
int sendall(int s, char *buf, int *len)
{
	int total = 0;        // how many bytes we've sent
	int bytesleft = *len; // how many we have left to send
	int n;

	while(total < *len) {
		n = send(s, buf+total, bytesleft, 0);
		if (n == -1) { break; }
		total += n;
		bytesleft -= n;
	}

	*len = total; // return number actually sent here

	return n==-1?-1:0; // return -1 on failure, 0 on success
}


/*the thread's function to handle a single client on stackServer prog.*/
void *serve_client(void *args){
	int numbytes = -2, fd;
	char buf[MAX_MSG_SIZE];
	struct thread_args *eta;
	dequeue *dq;
	char dqBuffer[MAX_MSG_SIZE];

	eta = (struct thread_args *)args;
	fd = eta->fd;
	dq = eta->dq;

	fd_set master, readfds;
	int maxfd;

	FD_ZERO(&master);
	FD_ZERO(&readfds);
	FD_SET(fd, &master);
	maxfd = fd;

	fprintf(stdout, "entered with fd: %d\n", fd);

	while (1){
		readfds = master;
		if (select(maxfd+1, &readfds, NULL, NULL, NULL) == -1){
			perror("select");
			return 0;
		}
		if (FD_ISSET(fd, &readfds)){
			numbytes = recv(fd, buf, MAX_MSG_SIZE ,0);


			fprintf(stdout, "DEBUG: %d\n", numbytes);     /*DEBUG*/

			if (numbytes == 0){    //connection closed
				break;
			}

			if (numbytes == -1){
				fprintf(stdout, "error handling client\n");
				break;
			}

			buf[numbytes] = '\0';
			fprintf(stdout, "buf in thread is == %s\n", buf);				/*DEBUG*/

			if (strcmp(buf, "exit") == 0){
				break;
			}
			else if ((pushCommand(buf)) == 1){
				extractPushStr(buf, dqBuffer);
				if (push(dq, dqBuffer) == -1){
					fprintf(stdout, "ERROR: couldn't push to the dequeue\n");
				}
				fprintf(stdout, "PUSHING\n");	//DEBUG
				strcpy(buf, "serverOK");
				buf[ strlen("serverOK") ] = '\0';
			}
			else if (strcmp(buf, "POP") == 0){
				fprintf(stdout, "POPING\n");	//DEBUG
				pop(dq);
				fprintf(stderr, "after pop\n");
				fflush(stdout);
				strcpy(buf, "serverOK");
				buf[ strlen("serverOK") ] = '\0';
			}
			else if (strcmp(buf, "TOP") == 0){
				fprintf(stdout, "entered TOP\n");	//DEBUG
				if (top(dq, dqBuffer, MAX_MSG_SIZE) == 0){
					strcpy(buf, "OUTPUT: stack is empty");
					buf[strlen("OUTPUT: stack is empty")] = '\0';
					dqBuffer[0] = '\0';
				}
				else {
					strcpy(buf, "OUTPUT: ");
					strcat(buf, dqBuffer);
					buf[strlen(dqBuffer) + strlen("OUTPUT: ")] = '\0';
				}
				fprintf(stdout, "TOPING\n");	//DEBUG

			}
			else if (strcmp(buf, "BOTTOM") == 0){
				printf("bottoming\n");
				if (bottom(dq, dqBuffer, MAX_MSG_SIZE) == 0){
					strcpy(buf, "OUTPUT: stack is empty");
					buf[strlen("OUTPUT: stack is empty")] = '\0';
					dqBuffer[0] = '\0';
				}
				else {
					strcpy(buf, "OUTPUT: ");
					strcat(buf, dqBuffer);
					buf[strlen(dqBuffer) + strlen("OUTPUT: ")] = '\0';
				}
				fprintf(stdout, "TOPING\n");	//DEBUG
			}
			else if (strcmp(buf, "POPBOTTOM") == 0){
				fprintf(stdout, "poping bottom\n");	//DEBUG
				popBottom(dq);
				strcpy(buf, "serverOK");
				buf[ strlen("serverOK") ] = '\0';
			}
			else if (pushBottomCommand(buf) == 1){
				extractPushStr(buf, dqBuffer);
				if (pushBottom(dq, dqBuffer) == -1){
					fprintf(stdout, "ERROR: couldn't push to the dequeue\n");
				}
				fprintf(stdout, "PUSHING to bottom\n");	//DEBUG
				strcpy(buf, "serverOK");
				buf[ strlen("serverOK") ] = '\0';
			}

			//send an answer to client
			numbytes = strlen(buf);
			printf("numbytes: %d\n", numbytes);
			fprintf(stdout,"dqBuffer: %s\n", buf);
			if (sendall(fd, buf, &numbytes) == -1){
				fprintf(stderr, "ERROR: failed to send the whole buffer\n"
						"sent only %d bytes out of %d, \n", numbytes, (int)(strlen(buf)+7));
			}
		}
	}

	close(fd);
	pthread_exit(NULL);
}


/* checks if the buffers prefix is "PUSH " */
int pushCommand(char *buffer){
	char *p = NULL;

	p = buffer;
	while (*p != '\0' && *p != ' ')
		p++;

	if (*p == '\0') return 0; //no space means not a push command

	if (*p == ' '){
		*p = '\0';   //mark the space char
		if (strcmp("PUSH", buffer) == 0){
			*p = ' ';    //remove the mark
			return 1;
		}
		else {
			*p = ' ';    //remove the mark
			return 0;
		}
	}

	return 0;    //if we got here it can't be a PUSH command return 0;
}

int pushBottomCommand(char *buffer){
	char *p = NULL;

	p = buffer;
	while (*p != '\0' && *p != ' ')
		p++;

	if (*p == '\0') return 0; //no space means not a push command

	if (*p == ' '){
		*p = '\0';   //mark the space char
		if (strcmp("PUSHBOTTOM", buffer) == 0){
			*p = ' ';    //remove the mark
			return 1;
		}
		else {
			*p = ' ';    //remove the mark
			return 0;
		}
	}

	return 0;    //if we got here it can't be a PUSH command return 0;
}

/*a function ver similar to puchCmd only it extracts the string
 * passed to push - we didn't want to clutter the while loop in serve_client more
 * than we had to
 */
void extractPushStr(char *Buf, char *destBuf){
	char *p = NULL;

	p = Buf;
	while (*p != '\0' && *p != ' ')
		p++;

	if (*p == '\0') return;
	else if (*p == ' '){
		p++;
		strcpy(destBuf, p);
		destBuf[strlen(p)] = '\0';
	}
	return;
}



